package com.douyang.service.impl;

import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.douyang.base.enumeration.GoodsTypeEnum;
import com.douyang.base.expcetion.TradeException;
import com.douyang.base.expcetion.VideoException;
import com.douyang.base.model.UserBaseInfo;
import com.douyang.base.utils.IdWorkerUtils;
import com.douyang.base.utils.UserUtil;
import com.douyang.dto.ChangStockDTO;
import com.douyang.dto.PayOrderDTO;
import com.douyang.dto.SeckKillDTO;
import com.douyang.model.SeckKill;
import com.douyang.mapper.SeckKillMapper;
import com.douyang.service.PayOrderBaseService;
import com.douyang.service.PayOrderDetailService;
import com.douyang.service.SeckKillService;
import com.douyang.util.LuaScriptUtil;
import com.douyang.vo.SeckKillVO;
import lombok.Synchronized;
import org.redisson.api.RLock;
import org.redisson.api.RScript;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.List;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author xiaoma
 * @since 2025-03-19
 */
@Service
public class SeckKillServiceImpl extends ServiceImpl<SeckKillMapper, SeckKill> implements SeckKillService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private LuaScriptUtil luaScriptUtil;

    @Autowired
    ThreadPoolExecutor threadPoolExecutor;

    @Autowired
    RedissonClient redissonClient;



    @Override
    public List<SeckKillVO> getSeckKillList() {
        List<SeckKill> seckKillList = list();
        if(CollectionUtils.isEmpty(seckKillList))return List.of();

        return seckKillList.stream().map(seckKill -> {
            SeckKillVO seckKillVO = new SeckKillVO();
            BeanUtils.copyProperties(seckKill, seckKillVO);
            return seckKillVO;
        }).toList();


    }

    @Override
    public void tryToSeckKill(Long killId) {
        redisAndLua(killId);

        //byCas(killId);
    }

    @Override
    public void addSeckKill(SeckKillDTO seckKillDTO) {
        if(seckKillDTO.getId()==null){
            SeckKill seckKill = new SeckKill();
            BeanUtils.copyProperties(seckKillDTO, seckKill);
            save(seckKill);
        }else{
            seckKillDTO.setStock(null);
            if(seckKillDTO.getStartTime().isBefore(LocalDateTime.now())){
                readWriteLock(seckKillDTO);
            }else{
                doubleDelete(seckKillDTO);
            }
        }

    }

    @Override
    public void deleteSeckKill(Long id) {
        removeById(id);
        redisTemplate.delete("seckKillData:" + id);
        redisTemplate.delete("seckKill:" + id);
    }

    @Override
    public void changStock(ChangStockDTO changStockDTO) {
        Long id = changStockDTO.getId();
        lambdaUpdate()
                .eq(SeckKill::getId, id)
                .setSql("stock = stock +" + changStockDTO.getStock())
                .update();
        luaScriptUtil.tryToDeduction("seckKill:" + id, changStockDTO.getStock().toString());
    }

    private void readWriteLock(SeckKillDTO seckKillDTO) {
        SeckKill seckKill = new SeckKill();
        BeanUtils.copyProperties(seckKillDTO, seckKill);
        updateById(seckKill);

        RLock rLock = redissonClient.getReadWriteLock("rwLock" + seckKillDTO.getId()).writeLock();
        try {
            rLock.lock();
            redisTemplate.delete("seckKillData:" + seckKillDTO.getId());
        }catch (Exception e){
            throw new TradeException(e.getMessage());
        }finally {
            rLock.unlock();
        }
    }


    private void doubleDelete(SeckKillDTO seckKillDTO) {
        redisTemplate.delete("seckKillData:" + seckKillDTO.getId());
        SeckKill seckKill = new SeckKill();
        BeanUtils.copyProperties(seckKillDTO, seckKill);
        updateById(seckKill);
        threadPoolExecutor.submit(()->{
            try {
                Thread.sleep(300);
                redisTemplate.delete("seckKillData:" + seckKillDTO.getId());
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
    }


    private void byCas(Long killId) {
        boolean update = lambdaUpdate()
                .eq(SeckKill::getId, killId)
                .ge(SeckKill::getStock, 1)
                .setSql("stock=stock-1")
                .update();
        if(!update){throw new TradeException("抢购失败");}
    }

    private void redisAndLua(Long killId) {
        Integer stock = getStock(killId);
        if(stock<=0)throw new TradeException("抢购失败，库存不足");
        try {
            Long result = luaScriptUtil.tryToDeduction("seckKill:" + killId, "-1");
            if(result==0){
                throw new TradeException("抢购失败，请尝试");
            }
        }catch (Exception e){
            throw new TradeException(e.getMessage());
        }


        String uuid = IdWorkerUtils.getInstance().createUUID();
        redisTemplate.opsForSet().add("checkIdList",uuid);

        UserBaseInfo loginUserBaseInfo = UserUtil.getLoginUserBaseInfo();
        PayOrderDTO payOrderDTO= PayOrderDTO.builder()
                .checkId(uuid)
                .userId(loginUserBaseInfo.getId())
                .userName(loginUserBaseInfo.getUserName())
                .goodsType(GoodsTypeEnum.SECKKILL)
                .goodsId(killId)
                .build();

        try {
            rabbitTemplate.convertAndSend("trade.direct","createPayOrder",payOrderDTO);
        }catch (Exception e){
            luaScriptUtil.tryToDeduction("seckKill:" + killId,"1");
            throw new TradeException(e.getMessage());
        }
    }


    private Integer getStock(Long killId) {
        RLock rLock = redissonClient.getReadWriteLock("rwLock" + killId).readLock();
        SeckKill seckKill;
        try {
            rLock.lock();
            String s1 = redisTemplate.opsForValue().get("seckKillData:" + killId);
            if(s1==null){throw new RuntimeException();}
            else seckKill= JSONUtil.toBean(s1,SeckKill.class);
        }catch (Exception e){
            seckKill = getById(killId);
            if(seckKill == null){throw new TradeException("无此商品");}
            redisTemplate.opsForValue().set("seckKillData:" + killId, JSONUtil.toJsonStr(seckKill),seckKill.getEndTime().toEpochSecond(ZoneOffset.of("+8"))-seckKill.getStartTime().toEpochSecond(ZoneOffset.of("+8")), TimeUnit.SECONDS);
        }finally {
            rLock.unlock();
        }


        if(seckKill.getStartTime().isAfter(LocalDateTime.now()) ){
            throw new TradeException("活动未开始");
        }

        if(seckKill.getEndTime().isBefore(LocalDateTime.now())){
            throw new TradeException("活动已结束");
        }

        String s = redisTemplate.opsForValue().get("seckKill:" + killId);

        if(s==null){
            seckKill = getById(killId);
            return Math.toIntExact(luaScriptUtil.tryToInsert("seckKill:" + killId, seckKill.getStock().toString()));
        }
        return Integer.parseInt(s);


    }
}
